/*
require  util.js
require  dataColumn.js
require  dragdrop.js
require  datagrid.js
require  multi-DataGrid.js
 */
if (!Util.isValid(QSAPI)) {
    var QSAPI = {
        Widget : {}
    };
} else if (!QSAPI.Widget) {
    QSAPI.Widget = {};
}

QSAPI.Widget.Tickerlist=function(container,config,callback,sdkid){
    this.grid=null;
    this.container=container;
    this.tValidation=null;
    this.callback={
        onAlertClick:function(rowid,value){//alert click event, don't include any argument
            var watchlistDataItem = this._getWatchlistDataItem(rowid);
            if(Util.isValid(workspaceManager,true,true)) workspaceManager.openAlertSetting();
        },
        afterCalcSummary:function(data){

        }
    }
    $.extend(true,this.callback,callback);
    this.subscribeID = sdkid;
    this.config={
        subscribeInvisibleTickers:false,//subscribeInvisible tickers
        showAlert:true,
        maxTicker:200,
        autoRefresh:true,
        invalidPrompt:"Invalid Ticker",
        dataType:"pull",
        initwidth:600,
        initheight:600,
        needStatic:true,
        viewKey:['Symbol','Name','LastPrice','Chg','Chg%','Volume'],
        displayLayout: GB_WL_LAYOUT.LIST, //'heatmap',
        remove:true,
		autoWidth:false,
        alternateRow:true,
        showInvalid :true,
		autoHeight:false,//fixHeight=true === autoHeight=false
		headerContextMenu:true,
		rowDragable: true,
		rowDropable: true,
        showFooter:false,
		sortable:true,
        groupBy: null,
        currency:GB_CURRENCY.USD,
        userCurrency:"usercur",
        cellResize:true,
        decimal:2,
        cellRearrange:true,
        summaryCalcMethod:{
            "TC":"TC",
            "P/L":"P/L",
            "P/Lpc":function(data){return data["P/L"]/data["TC"];},
            "MarketValue":"MarketValue",
            "Comm":"Comm"
        },
        highlightColumn:true,
        isFormat:false,
        isUnit:false,
        numberSeparate: ""//between number and unit
    };
	this.staticValue={
		minWidth:200,
        rowHeight:25,
        operateKey:"remove",
        dragGroup:"gridDDGroup",
        groupChars:"group",
        defaultValue:""
	};
    $.extend(this.config,config);
	this.lastHeight=0;
	this.firstTag=true;
    this.haveAlert=false;
    this.load=0;
    this.footerIsShow=this.config.footerRow;
    this.exchangeMap=null;//cache short name for avoiding to refetch
    this.sort_colnums=[];//sorted colnums
    this.visibleRows={};
    this.cacheView=[];//when change view from headmap
    this.calcTimer = null;
    if(this.config.autoRefresh){
        this.config.needStatic = true;
    }
    var self=this;
    this.dataInterface = {
        getStaticData:function(tickerObjects,callback){//get static from research DB
            if(QSAPI.DataManager){
                QSAPI.DataManager.getStaticData(tickerObjects,callback);
            }else{
                callback({});
            }
        },
        getColumnField:function(watchlistItem,field,related){//get morningstar data
            var returnValue = null;
            var gkey = watchlistItem.getTickerObject()[GB_WL_KEY.FIELD](GB_WL_KEY.GKEY);
            if(!Util.isValid(gkey,true,true)) return null;
            if(Util.isValid(related,true,true)){
                returnValue = QSAPI.DataManager._getData(gkey);
                if(returnvalue[related].value == 0){//don't  after
                    returnValue = "";
                }
                returnValue = returnValue[field].value;
            }else{
                returnValue = QSAPI.DataManager._getData(gkey,field).value;
            }
            return returnValue;
        },
        getExchange:function(tfcode){//get exchange short name
            if(!this.exchangeMap){
                this.exchangeMap= QSAPI.DataManager._getExchangeMap();
            }
            return this.exchangeMap[tfcode]||tfcode;
        },
        getPullData:function(tickerObjects,callback){
            QSAPI.DataManager._getSnapQuoteData(tickerObjects,callback);
        }
    };
    this.dataIsUpdated = {
        staticData:!this.config.needStatic,
        quoteData:false
    };//only for summary
    var lang = QS_Lang.watchlist;
    this.mapFairValueTxt = {
        "1": lang["mv_low"],
        "2": lang["mv_mdeiue"],
        "3": lang["mv_high"],
        "4": lang["mv_vhigh"],
        "5": lang["mv_extreme"],
        "NA": ""
    };
    this.gridLayOut={
        HEATMAP:"grid",
        LIST:"list"
    };
    this.miniCols = [{ //for heatmap
        header: lang["Symbol"],
        dataIndex: 'Symbol',
        dataType: 'string',
        className: 'cell-symbol',
		ellipsis:true
    }, {
        header: lang["lastprice"],
        dataIndex: 'LastPrice',
        dataType: 'float'
    }, {
        header: lang["chg%"],
        dataIndex: 'Chg%',
        className: 'cell-chgp',
        dataType: 'float'
    }];
    this._init();
};
QSAPI.Widget.Tickerlist.prototype={
    _init:function(){
        var html = '<div class="rtq-tickerlist rtq-wl"></div>';
        this.el = $(html).appendTo(this.container);
        this._initMiniCols(this.config.headMapCols);
        this._createGrid(this.config.initwidth,this.config.initheight,this._extendDataIndex(this.config.viewKey));
    },
    _initMiniCols:function(cols){
        if(this.config.headMapCols){
            var miniCols=[],tempCol=null;
            for(var i=0,l=this.config.headMapCols.length;i<l;i++){
                tempCol = QSAPI.Widget.Columns[this.config.headMapCols[i]];
                if(tempCol){
                    miniCols.push($.extend(true,{},tempCol));
                }
            }
            if(miniCols.length>0){
                this.miniCols=miniCols;
            }

        }
    },
    _extendDataIndex:function(views){
        var viewKey = [];
        for ( var k = 0, j = views.length; k < j; k++) {
            if(typeof views[k]!='object'){
                viewKey.push({
                    "dataIndex" : $.trim(views[k])
                });
            }else{
                viewKey.push(views[k]);
            }
        }
        return viewKey;
    },
    _onHeightChange:function(){
        if (this.firstTag) {
            Util.DelayRun('onload.tickerlist', this._loadHandler, 100, this);
        }else {
            Util.DelayRun('heightchange.tickerlist', this._heightChangeHandler, 100, this);
        }
    },
    _onStateChange:function(name,value){
        if (Util.isFunction(this.callback.onStateChange)){
            this.callback.onStateChange(name,value);
        }
    },
    _removeCell:function(cellkey){
        var temp=null;
        for (var i = 0,l = this.sort_colnums.length; i < l; i++) {
            temp = this.sort_colnums[i];
            if (temp.val == cellkey) {
                this.sort_colnums.splice(i, 1);
                break;
            }
        }
    },
    _genSortColumns:function(colnums){
        this.sort_colnums=[];
		var coltemp;
        for(var i=0,l=colnums.length;i<l;i++){
			coltemp=colnums[i];
            if (coltemp&&!coltemp.hide&&coltemp.type!=this.staticValue.operateKey && coltemp.dataIndex != this.config.groupBy){
                this.sort_colnums.push({
                    txt: coltemp.header,
                    val: coltemp.dataIndex,
                    selectable: true,
                    type: "ratio"
                });
            }
        }
    },
    _createGrid: function(w,h,dataIndexs){
        var self = this;
        var config = {
            outSideHeader: this.config.displayLayout == GB_WL_LAYOUT.HEATMAP ? true : false,
            column: this._convertColumns(dataIndexs),
            indexColKey: GB_WL_KEY.ID,
            width: w,
            height: h,
            rowHeight: this.staticValue.rowHeight,
            rowLayout: this.config.displayLayout == GB_WL_LAYOUT.HEATMAP ? this.gridLayOut.HEATMAP : this.gridLayOut.LIST,
            rowDragable: this.config.rowDragable,
            rowDropable: this.config.rowDropable,
            footerRow: this.config.showFooter,
			sortable:this.config.sortable,
			cellResize:this.config.cellResize,
			cellRearrange:this.config.cellRearrange,
            alternateRow:this.config.alternateRow,
			highlightColumn:this.config.highlightColumn,
            rowDragDropGroup: this.staticValue.dragGroup,
            headerContextMenu: this.config.headerContextMenu,
            groupAutoSort: true,
            groupDataIndex: this.config.displayLayout.indexOf(this.staticValue.groupChars) > -1 ? this.config.groupBy : null,
			autoWidth:this.config.autoWidth,
			autoHeight:this.config.autoHeight
        };
        var callbacks = {
            onRowRemove: function(id, row){
                var watchlistDataItem=self._getWatchlistDataItem(id);
                self._removeTicker(id);
                if(Util.isFunction(self.callback.onRowRemove)){
                    self.callback.onRowRemove(self.currentId,watchlistDataItem);
                }
            },
            onRowDragStart: function(e, proxy, row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
                var watchlistItem = watchlistDataItem[GB_WL_KEY.ITEM];
                var html = self.dataInterface.getColumnField(watchlistItem,GB_WL_KEY.UTICKER)||
                            self.dataInterface.getColumnField(watchlistItem,GB_WL_KEY.TTICKER);
                proxy.widgetID = self.subscribeID;// it is necessary
                proxy.item = watchlistDataItem[GB_WL_KEY.ITEM];
                proxy.rowDrag = true;
                proxy.html(html);
                if(Util.isFunction(self.callback.onRowDragStart)){
                    self.callback.onRowDragStart(e,proxy,watchlistDataItem);
                }
            },
            onRowRearrange: function(row, insertbeforerow){
                var rowID = row.data[GB_WL_KEY.ID],targetRowID=insertbeforerow.data[GB_WL_KEY.ID];
                GB_WLDM.moveItem(self.currentId,rowID,targetRowID,false);
                if(Util.isFunction(self.callback.onRowRearrange)){
                    self.callback.onRowRearrange(self._getWatchlistDataItem(rowID),
                        self._getWatchlistDataItem(targetRowID),false);//move to target above
                }
            },
            onCellSort: function(cellkey, order){
                self._reBindVisibleRows(true);
                self._updateItemsOrder(self.currentId);
                if(Util.isFunction(self.callback.onCellSort)){
                    self.callback.onCellSort(cellkey,order);
                }
            },
            onCellRearrange:function(cellkey,index){
                if(Util.isFunction(self.callback.onCellRearrange)){
                    self.callback.onCellRearrange(cellkey,index);
                }
            },
            afterResizeCol:function(cellkey,w){
                if(Util.isFunction(self.callback.afterResizeCol)){
                    self.callback.afterResizeCol(cellkey,w);
                }
            },
            onCellRemove: function(cellkey){
                self._removeCell(cellkey);
                if(Util.isFunction(self.callback.onCellRemove)){
                    self.callback.onCellRemove(cellkey);
                }
            },
            onRowDoubleClick: function(e, row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
				if(Util.isFunction(self.callback.onRowDoubleClick)){
					self.callback.onRowDoubleClick(e, watchlistDataItem);
				}
            },
            onRowContextmenu: function(e, row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
				if(Util.isFunction(self.callback.onRowContextmenu)){
					self.callback.onRowContextmenu(e,watchlistDataItem);
				}
            },
            onRowSelected: function(e, row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
				if(Util.isFunction(self.callback.onRowSelected)){
					self.callback.onRowSelected(e,watchlistDataItem);
				}
            },
            onScrollEnd: function(left, top){
                self._reBindVisibleRows(true);
                if(Util.isFunction(self.callback.onScrollEnd)){
                    self.callback.onScrollEnd(left, top);
                }
            },
            afterRebindData: function(DOMMap, rows){
				self._onHeightChange();
				if(Util.isFunction(self.callback.afterRebindData)){
                    self.callback.afterRebindData();
                }
            },
            afterGroupDrop:function(){
                self._updateItemsOrder(self.currentId);
                if(Util.isFunction(self.callback.afterGroupDrop)){
                    self.callback.afterGroupDrop();
                }
            },
			onSortedbyCell:function(cellkey,lastorder,callback){
                 if(Util.isFunction(self.callback.onSortedbyCell))
                    self.callback.onSortedbyCell(cellkey,lastorder,callback);
            },
			onCellClick:function(cell){
				if(Util.isFunction(self.callback.onCellClick)){
                    self.callback.onCellClick(cell);
                }
			}
        };
        if(Util.isFunction(this.callback.onRowMouseover)){
            callbacks["onRowMouseover"]=function(e,row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
                self.callback.onRowMouseover(e,watchlistDataItem);
            };
        }
        if(Util.isFunction(this.callback.onRowMouseout)){
            callbacks["onRowMouseout"]=function(e,row){
                var watchlistDataItem=self._getWatchlistDataItem(row.data[GB_WL_KEY.ID]);
                self.callback.onRowMouseout(e,watchlistDataItem);
            };
        }
        if (this.config.displayLayout.indexOf('wrap') > -1) {
            this.grid = new Util.MultiDataGrid(this.el, [], {
                columnSeparate: true,
                width: w,
                height: h,
                gRowsHeight: this.staticValue.rowHeight,
                gColumns: config.column,
                footerRow: this.config.showFooter,
                footerIsShow:this.footerIsShow,
                indexColKey: GB_WL_KEY.ID,
                gConfig: config,
                groupAutoSort: true,
				fixHeight:!this.config.autoHeight,
                groupDataIndex: this.config.groupBy
            }, callbacks);
        }else {
            this.grid = new Util.DataGrid(this.el, {}, config, callbacks);
        }
        //add heatmap style
        if (this.config.displayLayout == GB_WL_LAYOUT.HEATMAP) {
            this.grid.el.addClass('wl-heatmap');
        }else {
            this.grid.el.removeClass('wl-heatmap');
        }
        //add M* sector icon style
        if (this.config.groupBy == GB_WL_GROUP.SECTOR) {
            this.el.addClass('wl-mstar-sector');
        }else {
            this.el.removeClass('wl-mstar-sector');
        }
    },
    _getWatchlistDataItem:function(rowID){
        return GB_WLDM.getWatchlistDataItem(this.currentId,rowID);
    },
    _calcSummaryListen:function(){
        if(!Util.isValid(this.calcTimer,true,true)){
            this.showLoading();
            var self=this;
            var listenFunction=function(){
                if(self._summaryIsUpdated()){
                    self._triggerCalcSummary();
                    if(self.calcTimer){
                        clearInterval(self.calcTimer);
                        self.calcTimer=null;
                        self.hideLoading();
                    }
                }
            };
            listenFunction();
            this.calcTimer = setInterval(function(){
                listenFunction();
            },1000);
        }
    },
    _updateItemsOrder: function(id){
        var roIDs = this.grid.getIDs();
        GB_WLDM.sortItems(id, roIDs);
    },
    _summaryIsUpdated:function(){
        return this.dataIsUpdated.quoteData&&this.dataIsUpdated.staticData;
    },
    _setSummaryStatus:function(status){
        $.extend(this.dataIsUpdated,status);
    },
    _showAlert:function(leftMaxLimit,tickerLength,forceAlert){
        if (tickerLength > leftMaxLimit&&this.config.showAlert&&(!this.haveAlert||forceAlert)) {
            this.haveAlert = true;
            Util.message(String.format(QS_Lang.text["watchlistlimit"], this._getMaxLimit()));
        }
    },
    _setDefaultCell:function(item){
        if(!item[this.config.userCurrency]){
            item[this.config.userCurrency] = this.config.currency;
        }
    },
    _reBindTickers:function(watchlistDataItems){
        this.visibleRows = {}; // clear old visible rows
        this.userData = {};
        var tickerLength=watchlistDataItems.length,maxLimit=this._getMaxLimit();
        this._showAlert(maxLimit,tickerLength);
        var maxTicker = Math.min(tickerLength,maxLimit);
        var watchlistItem=null,item={},items=[],rowID=null,userData={};
        for (var i = 0; i < maxTicker; i++) {
            watchlistItem = watchlistDataItems[i][GB_WL_KEY.ITEM];
            if(!this.config.showInvalid&&!watchlistItem.isValid()) { if(maxTicker<tickerLength) {maxTicker++;} continue;}
            rowID = watchlistDataItems[i][GB_WL_KEY.ID];
            this.userData[rowID] = {defaultCurrency:this.config.currency};
            userData =  watchlistItem.getCustomColumns();
            item={};
            item[GB_WL_KEY.ID] = rowID;
            item["Symbol"] = this._getField(watchlistDataItems[i],"Symbol");
            for(var field in userData){//bind user customized data
                item[field] =  userData[field];
                this.userData[rowID][field] = userData[field];
            }
            this._setDefaultCell(item);
            //add grouping data
            if (this.config.groupBy) {
                item[this.config.groupBy] = this._getField(watchlistDataItems[i],this.config.groupBy);
            }
            items.push(item);
        } 
        //also need clear grid
        this.grid.reBindData(items);
        this._reBindVisibleRows();
        this._manualUpdate();   
        this._onHeightChange();
        this._addInvalidFlag(watchlistDataItems,maxTicker);
    },
    _addTickers:function(watchlistDataItems){
        var tickerLength=watchlistDataItems.length,maxLimit=this._getMaxLimit();
        this._showAlert(maxLimit-this._getTickerLength(),tickerLength,true);
        var maxTicker = Math.min(tickerLength,maxLimit-this._getTickerLength());
        var watchlistItem=null,item={},rowID=null,userData={};
        for (var i = 0; i < maxTicker; i++) {
            watchlistItem = watchlistDataItems[i][GB_WL_KEY.ITEM];
            if(!this.config.showInvalid&&!watchlistItem.isValid()) { if(maxTicker<tickerLength) {maxTicker++;} continue;}
            if(!watchlistDataItems[i][GB_WL_KEY.DB]){
                GB_WLDM.insertItems(this.currentId,[watchlistDataItems[i]],true);
            }
            rowID = watchlistDataItems[i][GB_WL_KEY.ID];
            this.userData[rowID] = {defaultCurrency:this.config.currency};
            userData =  watchlistItem.getCustomColumns();
            item={};
            item[GB_WL_KEY.ID] = rowID;
            item["Symbol"] = this._getField(watchlistDataItems[i],"Symbol");
            for(var field in userData){//bind user customized data
                item[field] =  userData[field];
                this.userData[rowID][field] = userData[field];
            }
            this._setDefaultCell(item);
            //add grouping data
            if (this.config.groupBy) {
                item[this.config.groupBy] = this._getField(watchlistDataItems[i],this.config.groupBy);
            }
            this.grid.addRow(item,rowID,0);
        } 
        if(maxTicker>0){
            this._reBindVisibleRows();
            this._addInvalidFlag(watchlistDataItems,maxTicker);
            this._onHeightChange();
        }
    },
    _getMaxLimit:function(){
        var maxCount = GB_WLDM.getMaxTickers(this.currentId);
        if(!Util.isValid(maxCount,true,true,true,true)){
            maxCount = this.config.maxTicker;
        }
        return maxCount;
    },
    _getTickerLength: function(){
        return this.grid.getRowLength();
    },
    _heightChangeHandler:function(){
         if(this.config.autoHeight&&Util.isFunction(this.callback.onHeightChange)){
            var height=this.el.height(),width=this.el.width();
            if(this.lastHeight!=height){
                this.callback.onHeightChange({
                    component: "tickerList",
                    height: height,
                    width : width
                });
                this.lastHeight=height;
            }
        }
    },
    _loadHandler:function(){
        if(Util.isFunction(this.callback.onLoad)){
            this.callback.onLoad({
                component: "tickerList",
                height: this.el.height(),
                width:this.el.width(),
                minWidth : this.staticValue.minWidth
            });
            this.firstTag = false;
        }
    },
    _triggerCalcSummary:function(){
        var returnValue={},method=null,dataPoint=null,relatedField=null,isFunction=false,hasRelated=false;
        for(var point in this.config.summaryCalcMethod){
            dataPoint = this.config.summaryCalcMethod[point];
            if(Util.isFunction(dataPoint)) continue;
            returnValue[point]=0.00;
            relatedField = QSAPI.Widget.Columns[dataPoint]["relatedField"];
            method =  QSAPI.Widget.Columns[dataPoint]["calcMethod"];
            hasRelated = Util.isValid(relatedField,true,true);
            isFunction = Util.isFunction(method);
            var tempData=null;
            for(var rowID in this.userData){
                if(isFunction){
                    tempData = method(this.userData[rowID],hasRelated?relatedField[0]:"",hasRelated?this.userData[rowID][relatedField[0]]:"");
                }else{
                    tempData = this.userData[rowID][dataPoint];
                }
                if(Util.isValid(tempData,true,true,true)&&!isNaN(tempData)){
                    returnValue[point]+=parseFloat(tempData);
                }
            }
        }
        for(var point in this.config.summaryCalcMethod){
            dataPoint = this.config.summaryCalcMethod[point];
            if(Util.isFunction(dataPoint)){
                returnValue[point] = dataPoint(returnValue);
            }
            if(isNaN(returnValue[point])||!Util.isValid(returnValue[point],true,true,true)){
                returnValue[point] = this.staticValue.defaultValue;
            }else{
                returnValue[point] = parseFloat(returnValue[point]).toFixed(this.config.decimal);
            }
            
        }
        for(var point in this.config.summaryCalcMethod){
            if(QSAPI.Widget.Columns[point].dataType=="float"){
                returnValue[point] = isNaN(returnValue[point])?this.staticValue.defaultValue:Util.formatDoubledata(returnValue[point],null,this.config.decimal,this.config.isFormat,this.config.isUnit,this.config.numberSeparate,true,false);
            }
        }
        if(this.config.showFooter){
            this.grid.reBindFooter(returnValue);
        }
        if(Util.isFunction(this.callback.afterCalcSummary)){
            this.callback.afterCalcSummary(returnValue);
        }
    },
    _calculateHeadMapBGColor:function(data){
        var L1 = 3,L2 = 2,L3 = 1,gColors = ['rtq-grid-cell-r1', 'rtq-grid-cell-r2', 'rtq-grid-cell-r3'];
        var rColors = ['rtq-grid-cell-g1', 'rtq-grid-cell-g2', 'rtq-grid-cell-g3'];
        if (data >= 0) {
            if (data > L2) {
                bg = rColors[0];
            }else if (data > L3 && data < L2) {
                bg = rColors[1];
            }else if (data < L3) {
                bg = rColors[2];
            }
        }else {
            if (data < -L2) {
                bg = gColors[0];
            }else if (data < -L3 && data > -L2) {
                bg = gColors[1];
            }else if (data > -L3) {
                bg = gColors[2];
            }
        }
        return bg;
    },
    _updateDOM: function(rowID,field,data,oldData,type,needFlash,strFormat){
        this.userData[rowID][field] = data;
        if (!this.grid.DOMMap[rowID]) {
            return;
        }
        var el = this.grid.DOMMap[rowID][field],addel=null;
        if(field=="Volume"){//vol(mil),vol
            addel=this.grid.rows[rowID].cells["RVolume"];
        }else if(field == "Chg"){
            addel=this.grid.DOMMap[rowID]["Up/Down"];
        }else if(!el&&!addel){
            return;
        }
        var orginData=null,adddata=null,
        watchlistItem = this._getWatchlistDataItem(rowID)[GB_WL_KEY.ITEM],
        _flashMethod = !needFlash? function(){}: Util.flash,
        colConfig = QSAPI.Widget.Columns[field],
        securitytype=this.dataInterface.getColumnField(watchlistItem,"type"),
        defaultfloat=this.config.decimal,
        nubtag=(colConfig&&colConfig["dataType"]=="float")?true:false;
        switch (field) {
            case 'Volume':
                _flashMethod(el, null, null);
                if(addel){
                    _flashMethod(addel, null, null);
                    adddata=data;
                }
                data = data / 1000000;
                defaultfloat=4;//as default
                break;
            case 'AskSize':
            case 'BidSize'://i
                _flashMethod(el, null, null);
                break;
            case 'AskPrice':
            case 'BidPrice':
            case 'Spread':
                _flashMethod(el, null, null);
                break;
            case 'Time':
                _flashMethod(el, null, null);
                data = Util.FDate(data);//s
                break;
            case 'LastPrice':
                if (this.config.displayLayout != 'heatmap') {
                    _flashMethod(el, data, oldData);
                }
                break;
            case 'AfterTPrice':
                var tempData = this.dataInterface.getColumnField(watchlistItem,"AfterTPrice","showAlert");
                break;
            case 'Chg':
            case 'Chg%':
                //flash background for heatmap
                if (this.config.displayLayout == GB_WL_LAYOUT.HEATMAP) {//s
                    if (el) {
                        var bg = this._calculateHeadMapBGColor(data);
                        var rowDOM = el.parentNode.parentNode;
                        rowDOM.className = rowDOM.className.replace(/rtq-grid-cell-(r|g)\d/g, '');
                        rowDOM.className += ' ' + bg;
                    }
                }
                else {
                    if (el) {
                        if (needFlash) {
                            Util.flashWithDirectional(el, data, oldData, data>= 0);//flash with color
                        }else {
                            el.style.color = data >= 0 ? QS_Style.greenFontColor : QS_Style.redFontColor;
                        }
                    }
                    if (field == "Chg") { // update the up/down column;
                        var upd = Util.isValid(data,true,true,true,true)?(data>0?"up":"down"):"";
                        if(upd.length>0&&addel){
                            addel.innerHTML = String.format('<a class="updown {0}" href="javascript:void(0)">{1}</a>',upd,upd);
                        }
                    }
                }
                break;
            case 'Alert':
                data = this._getAlertData(rowID,data);//s
                break;
            case 'st200'://Mstar Rating
                var starStr = '';
                for (var i = 0; i < data; i++) {
                    starStr += '*';
                }
                data = '<span class="star' + data + '">' + starStr + '</span>';//s
                break;
            case 'st201':// Mstar Fair Value Un
                data = this.mapFairValueTxt[data];//s
                break;
            case 'R/D'://s
                break;
            case 'performanceId':
                break;
            default:
                break;
        }
        //update data
        if (el||addel) {
            orginData = data;
            if(nubtag){//data
                if(colConfig&&Util.isValid(colConfig["decimal"],true,true)){
                    defaultfloat=colConfig["decimal"];
                }
                data = isNaN(data)?this.staticValue.defaultValue:Util.formatDoubledata(data,securitytype,defaultfloat,this.config.isFormat,this.config.isUnit,this.config.numberSeparate,true,false);
            }
            if(addel&&adddata&&field=="Volume"){//RVolume
                orginData = adddata;

                adddata = isNaN(adddata)?this.staticValue.defaultValue:Util.formatDoubledata(adddata,securitytype,0,this.config.isFormat,this.config.isUnit,this.config.numberSeparate,true,false);
                if(colConfig["beforeRender"]){
                    adddata = colConfig["beforeRender"](this.userData[rowID],adddata);
                }
                addel.updateCell(adddata,orginData);
            }
            if(el){
                if(colConfig["beforeRender"]){
                    data = colConfig["beforeRender"](this.userData[rowID],data);
                }
                this.grid.rows[rowID].cells[field].updateCell(data,orginData);
            }
        }
    },
    _getField:function(watchlistDataItem,field){//get customized name and symbol
        var watchlistItem = watchlistDataItem[GB_WL_KEY.ITEM],rowID = watchlistDataItem[GB_WL_KEY.ID];
        if(field=="Symbol"){//symbol
            var uticker = this.dataInterface.getColumnField(watchlistItem,GB_WL_KEY.UTICKER);
            if(Util.isValid(uticker,true,true)){
                return uticker;
            }else{
                var tticker = this.dataInterface.getColumnField(watchlistItem,GB_WL_KEY.TTICKER);
                if(Util.isValid(tticker,true,true)){
                    return tticker;
                }else{
                    return watchlistItem.getSymbol();
                }
            }
        }else{
            var data = this.dataInterface.getColumnField(watchlistItem,field);
            return this.userData[rowID][field]||(Util.isValid(data,true,true,true)?data:"");
        }
    },
    _getDataFromCache:function(watchlistItem,field){
        var returnValue = null;
        if(!this.config.autoRefresh){
            var rowIDs = GB_WLDM.getRowIdsBySymbol(this.currentId,watchlistItem.getSymbol());
            if(rowIDs.length>0&&this.userData[rowIDs[0]]){
                 returnValue = this.userData[rowIDs[0]][field];
            }
        }
        return returnValue;
    },
    _needUpdateCol:function(colConfig){
        return  !($.inArray(colConfig.dataIndex,[GB_WL_KEY.ID,"Symbol"])>-1||colConfig.type==this.staticValue.operateKey);
    },
    _addInvalidFlag: function(watchlistDataItems,maxTicker){
        if(!this.config.showInvalid) return;
        var rowID=null;
        for (var i = 0; i < maxTicker; i++) {
            rowID = watchlistDataItems[i][GB_WL_KEY.ID];
            if (!watchlistDataItems[i][GB_WL_KEY.ITEM].isValid()) {
                var dom = this.grid.DOMMap[rowID]['Symbol'];
                var el = $(dom);
                el.addClass('invalid-symbol');
                dom.innerHTML = '<a href="javascript:void(0)" title="'+this.config.invalidPrompt+'">' + el.html() + '</a>';
            }
        }
    },
    //get visible rows/tickers and store into this.visibleRows, should be called on scroll/resize/rebind.
    _cacheVisibleRows: function(isDelay, callback){
        var self = this;
        var checkSubscribe = function(){
            if(self.config.autoRefresh){
                var newVisibleRows = self.config.subscribeInvisibleTickers ? self.grid.rows : self.grid.getVisibleRow();
                var needSubscribeTicker = [],needVisibleTicker={},needUnSubscribeTicker = [],wlitem=null;
                for (var r in newVisibleRows) {
                    if(!Util.isValid(newVisibleRows[r],true,true)) continue;
                    wlitem = self._getWatchlistDataItem(r)[GB_WL_KEY.ITEM];
                    needVisibleTicker[r]=true;
                    if (!self.visibleRows[r]) {
                        needSubscribeTicker.push(wlitem.getTickerObject());
                    }
                }
                for (var r in self.visibleRows) {
                    wlitem = self._getWatchlistDataItem(r)[GB_WL_KEY.ITEM];
                    if (!newVisibleRows[r]&&self.visibleRows[r]) {
                        needUnSubscribeTicker.push(wlitem.getTickerObject());
                    }
                }
                self._subscribeTicker(needSubscribeTicker);
                self._unsubscribeTicker(needUnSubscribeTicker);
                self.visibleRows = needVisibleTicker;
            }
        };
        if (isDelay) {
            //refresh visible rows, sync for datagrid resize
            this._cacheVisibleRowsTimer = 0;
            clearTimeout(this._cacheVisibleRowsTimer);
            this._cacheVisibleRowsTimer = setTimeout(function(){
                checkSubscribe();
                if (Util.isFunction(callback)) {
                    callback();
                }
            }, 100);
        }
        else {
            checkSubscribe();
        }
    },
    _subscribeTicker:function(tickerObjects,callback){
        if(this.config.autoRefresh){
            var subscribe="subscribePush";
            if(this.config.dataType=="pull"){
                subscribe ="subscribePull";
            }
            QSAPI.DataManager[subscribe](this,tickerObjects,{
                onFailure:function(result){
                    //you don't need to process
                },
                onSuccess:function(){
                    if(Util.isFunction(callback)){
                        callback(data);
                    }
                }
            });
        }
    },
    _unsubscribeTicker:function(tickerObjects,callback){
        if(this.config.autoRefresh){
            var unsubscribe="unSubscribePush";
            if(this.config.dataType=="pull"){
                unsubscribe = "unSubscribePull";
            }
            QSAPI.DataManager[unsubscribe](this,tickerObjects);
        }
    },
    _convertColumns:function(cols){
        var gridCol=[],includeID=false;
        if (this.config.displayLayout == GB_WL_LAYOUT.HEATMAP) { //|| this.displayLayout.indexOf('wrap') > -1){
            this.cacheView = cols.slice();
            gridCol =  $.merge([],this.miniCols);
        }else {
            this.cacheView = [];
            var offset = -1,dataindex=null,isIncludeGroupField=false;
            for (var i = cols.length-1; i >=0; i--) {
                dataindex = cols[i].dataIndex;
                if(dataindex == this.staticValue.operateKey){
                    offset = i ;
                }else if(dataindex == this.config.groupBy) {
                    isIncludeGroupField = true;
                }else if(dataindex == GB_WL_KEY.ID){
                    includeID = true;
                }
                if(offset != -1&&isIncludeGroupField) break;
                $.extend(true,cols[i],QSAPI.Widget.Columns[dataindex]);
            }
            //col-delbtn
            if (this.config.remove&&(offset==-1)) {
                cols.push(QSAPI.Widget.Columns[this.staticValue.operateKey]);
            }else if(!this.config.remove&&(offset!=-1)){
                // not include the this.staticValue.operateKey column.
                cols.splice(offset, 1);
            }
            gridCol=$.merge([],cols);
            //group by
            if(this.config.groupBy&&!isIncludeGroupField){
                gridCol.push($.extend(true,{hide:true},QSAPI.Widget.Columns[this.config.groupBy]));
            }
        }
        this._genSortColumns(gridCol);
        if(!includeID){
            gridCol.push({
                header: 'id',
                dataIndex: GB_WL_KEY.ID,
                hide: true
            });
        }
        return gridCol;
    },
    _bindDataRow:function(watchlistItem,needFlash){  //bind data from gData
        var aData = {};
        var cols = this.grid.getColumnCfg(),tempCol={},field=null,quotedatalist=null,relatedField=null;
        for (var j=0,h=cols.length;j<h;j++) {
            relatedField=[];
            tempCol = cols[j];
            field = tempCol.dataIndex;
            if(!this._needUpdateCol(tempCol)){
                continue;
            }else if (field == "Up/Down"){
                field = "Chg";
            }else if(field =="RVolume"){
                field = "Volume";
            }
            if(!tempCol.isCustom){//market data
                relatedField.push(field);
            }
            if(tempCol.relatedField){
                $.merge(relatedField,tempCol.relatedField);//market data
            }
            for(var i=0,l=relatedField.length;i<l;i++){
                field = relatedField[i];
                if(!aData[field]){
                    quotedatalist = this.dataInterface.getColumnField(watchlistItem,field);
                    if(!Util.isValid(quotedatalist,true,true)){//from cache
                        quotedatalist = this._getDataFromCache(watchlistItem,field);
                    }
                    if(Util.isValid(quotedatalist,true,true)){
                        aData[field] = {
                            dataType: tempCol.dataType,
                            value: quotedatalist
                        };
                    }
                }
            }
        }
        this.updateData(watchlistItem.getTickerObject(),aData,needFlash);//if tickerObject is invalid,quotedatalist is null
    },
    _bindUserData:function(rowID,quoteField,value,ovalue,type,needFlash){//bind user customized data
        var cols = this.grid.getColumnCfg(), tempCol=null,field=null,relatedField=[],calcMethod=null,
        watchlistDataItem =  this._getWatchlistDataItem(rowID),
        watchlistItem = watchlistDataItem[GB_WL_KEY.ITEM],cvalue=null,covalue=null,
        customData=this.userData[rowID],tempField=null;
        
        for (var c in cols) {
            tempCol = cols[c],field = tempCol.dataIndex;
            if(field==GB_WL_KEY.ID||tempCol.type=="remove") continue;
            if(tempCol.isCustom&&Util.isFunction(tempCol.calcMethod)){//only update un-morningstar data
                calcMethod = tempCol.calcMethod;
                relatedField=tempCol.relatedField;
                if(watchlistItem.isValid()&&Util.isValid(relatedField,true,true)&&relatedField.length>0){
                    if($.inArray(quoteField,relatedField)>-1){
                        for(var i = 0,l=relatedField.length;i<l;i++){
                            tempField = relatedField[i];
                            if(!Util.isValid(customData[tempField],true,true)){
                                customData[tempField] = this._getField(watchlistDataItem,tempField);
                            }
                        }
                        covalue = customData[field];//can't get the ovalue
                        cvalue = calcMethod(customData);
                        this._updateDOM(rowID,field,cvalue,covalue,cols[c].dataType,needFlash);
                    }
                }else{
                    covalue = customData[field];//can't get the ovalue
                    cvalue = calcMethod(customData);
                    this._updateDOM(rowID,field,cvalue,covalue,cols[c].dataType,needFlash);
                }
            }
        }
    },
    _reBindVisibleRows:function(isDelay){
        if(isDelay){
            var self=this;
            this._cacheVisibleRows(isDelay,function(){
                self._bindData();
            });
        }else{
            this._cacheVisibleRows();
            this._bindData();
        }
    },
    _removeTicker:function(rowID){
        var watchlistItem = this._getWatchlistDataItem(rowID)[GB_WL_KEY.ITEM],symbol = watchlistItem.getSymbol();
        this._unsubscribeTicker([watchlistItem.getTickerObject()]);
        GB_WLDM.deleteItems(this.currentId,[rowID]);
        delete this.visibleRows[rowID];
        delete this.userData[rowID];
        var self=this;
        this._reBindVisibleRows(true);
        Util.DelayRun('heightchange.tickerlist', this._heightChangeHandler, 100, this);
    },
    _bindData:function(){  //force bind grid data
        this.showLoading();
        var rows=this.config.subscribeInvisibleTickers?this.grid.rows:this.visibleRows,list=[],watchlistDataItem=null;
        var self=this;
        this._getStatic(rows,function(){
            for (var i in rows) {
                if(!Util.isValid(rows[i],true,true)) continue; //deleted row is null, need filter
                self._bindDataRow(self._getWatchlistDataItem(i)[GB_WL_KEY.ITEM],false);
            }
            self.hideLoading();
        });
    },
    _manualUpdate:function(){
        if(this.config.autoRefresh) return;
        var rows = this.config.subscribeInvisibleTickers ? this.grid.rows : this.grid.getVisibleRow();
        var tickerObjects = [],self=this;
        for (var r in rows) {
            if(!Util.isValid(rows[r],true,true)) continue;
            wlitem = this._getWatchlistDataItem(r)[GB_WL_KEY.ITEM];
            if (wlitem.isValid()){
                tickerObjects.push(wlitem.getTickerObject());
            }
        }
        if (tickerObjects.length > 0) {//get quote data
            this.showLoading();
            this.dataInterface.getPullData(tickerObjects,function(data){
                for(var i = 0,l=tickerObjects.length;i<l;i++){
                    self.updateData(tickerObjects[i],data[i]);
                }
                self._setSummaryStatus({quoteData:true});
                self.hideLoading();
            });
        }else{
            this._setSummaryStatus({staticData:true,quoteData:true});
        }
    },
    _getStatic:function(rowIDs,callback){
        if(this.config.needStatic){//subscribe all tickers
            var wathclistItem=null,list=[],self=this;
            for (var i in rowIDs) {
                if(!Util.isValid(rowIDs[i],true,true)) continue;
                wathclistItem = this._getWatchlistDataItem(i)[GB_WL_KEY.ITEM];
                if (wathclistItem.isValid()){
                    list.push(wathclistItem.getTickerObject());
                }
            }
            if (list.length > 0) {
                this.dataInterface.getStaticData(list, function(){
                    self._setSummaryStatus({staticData:true});
                    callback();
                });
            }else{
                this._setSummaryStatus({staticData:true});
            }
        }else{
             callback();
        }
    },
    _getIDsFromWatchlistDataItems:function(watchlistDataItems){//Map
        var ids={};
        for(var i=0,l=watchlistDataItems.length;i<l;i++){
            ids[watchlistDataItems[i][GB_WL_KEY.ID]]=true;
        }
        return ids;
    },
    _groupBy:function(dataIndex,callback){
        if(!QSAPI.Widget.Columns[dataIndex]){  // not support this dataIndex.
            return false;
        }
        this.config.groupBy = dataIndex;
        this.grid.setGroup(dataIndex);
        this.grid.setColumnCfg(this._convertColumns(this.grid.getColumnCfg())); //refresh the column for grouping
        //add M* sector icon style
        if (this.config.groupBy == GB_WL_GROUP.SECTOR) {
            this.el.addClass('wl-mstar-sector');
        }else {
            this.el.removeClass('wl-mstar-sector');
        }
        this.reLoad();
        if(typeof callback=='function'){
            callback();
        }
    },
    groupBy:function(dataIndex,callback){
        if(this.config.displayLayout.indexOf(this.staticValue.groupChars) == -1 || this.config.groupBy==dataIndex || !Util.isValid(dataIndex,true,true,true)) return;
        var self=this;
        this._onStateChange("groupBy",dataIndex);
        this.showLoading();
        this._getStatic(this.grid.rows,function(){
            self._groupBy(dataIndex, callback);
            self.hideLoading();
        });
    },
    calcSummary:function(callback){//it only support manual refresh
        if(this.config.subscribeInvisibleTickers&&!this.config.autoRefresh){
            this.callback.afterCalcSummary=callback;
            this._calcSummaryListen();
        }
    },
    hideFooter:function(){
        if(this.config.showFooter){
            this.footerIsShow=false;
            this.grid.hideFooter();
        }
    },
    showFooter:function(){
        if(this.config.showFooter){
            this.footerIsShow=true;
            this.grid.showFooter();
        }
    },
    sort:function(dataIndex,direction){ //direction :up:"ASC",down:"DESC"
        this.grid.sortByCell(dataIndex,direction);
    },
    changeViewType:function(viewType,callback){//list,listwrap,listgroup,list wrap&group,heatmap
        if(this.config.displayLayout!=viewType){
            this.config.displayLayout = viewType || GB_WL_LAYOUT.LIST;
            var width = this.grid.container.width();
            var height = this.grid.container.height();
            this.empty();
            this.grid.el.remove();       
            this._createGrid(width, height, this._extendDataIndex(this.cacheView.length>0?this.cacheView:this.grid.getColumnCfg()));
            this._onStateChange("displayLayout",viewType);
            this.reLoad();
        }    
    },
    empty:function(){
        this.grid.empty();
    },
    setColumns:function(cols){
        this.grid.setColumnCfg(this._convertColumns(cols));
        this.reLoad();
    },
    getSortColumns:function(){
       return this.sort_colnums;
    },
	getColumnsView:function(){
		var colnums=this.grid.getColumnCfg(),coltemp=null,returnvalue=[];
        for(var i=0,l=colnums.length;i<l;i++){
            coltemp=colnums[i];
            if (coltemp&&!coltemp.hide&&coltemp.type!=this.staticValue.operateKey&&coltemp.dataIndex!=this.config.groupBy){
                returnvalue.push({
                    dataIndex: coltemp.dataIndex,
                    width: coltemp.width
                });
            }
        }
		return returnvalue;
	},
    getlastSortCellKey:function(){
        return this.grid.lastSortCellKey;
    },
    getSortOrder:function(){
        return this.grid.lastSortOrder;
    },
    setLoadingHandle:function(showHandle,hiddenHandle){
        var self=this;
        if(Util.isFunction(showHandle)){
            this.showLoading=function(){
                if(self.load==0){
                   showHandle.apply(self);
                }
                self.load++;
            };
        }
        if(Util.isFunction(hiddenHandle)){
            this.hideLoading=function(){
                self.load--;
                if(self.load==0){
                   hiddenHandle.apply(self);
                }
            };
        }
    },
    setColumns:function(keys){ // [{dataIndex:"LastPrice",width:60},{.}]
        this._onStateChange("viewKey",keys.slice);
        this.grid.setColumnCfg(this._convertColumns(this._extendDataIndex(keys)));
        this.reLoad();
    },
    reLoad:function(){
        var self = this;
        this.showLoading();
        GB_WLDM.getWatchlistData(this.currentId,function(watchlistData){
            self.reBindData(watchlistData.getItems());
            self.hideLoading();
        });  
    },
    removeTickers:function(rowIDs){
        for(var i=0,l=rowIDs.length;i<l;i++){
            this.grid.removeRow(rowIDs[i]);
            this._removeTicker(rowIDs[i]);
        }
    },
    addTickers:function(watchlistDataItems){
        if(Util.isValid(this.currentId,true,true,true)){
            this.showLoading();
            this._setSummaryStatus({staticData:!this.config.needStatic,quoteData:false});
            if(this.config.displayLayout.indexOf(this.staticValue.groupChars) > -1){
                var self = this;
                this._getStatic(this._getIDsFromWatchlistDataItems(watchlistDataItems),function(){
                    self._addTickers(watchlistDataItems);
                    self.hideLoading();
                });
            }else{
                this._addTickers(watchlistDataItems);
                this.hideLoading();
            }
        }else{
            throw new Error("Invoke before, you need set watchlist id by setWatchlistId methed.");
        }
    },
    refresh:function(){
        this._manualUpdate();
    },
    reBindData: function(watchlistDataItems){// bind tickersList to grid, it will check all the ticker with ticker validation.
        if(Util.isValid(this.currentId,true,true,true)){
            this.showLoading();
            this._setSummaryStatus({staticData:!this.config.needStatic});
            if(this.config.displayLayout.indexOf(this.staticValue.groupChars) > -1){
                var self = this;
                this._getStatic(this._getIDsFromWatchlistDataItems(watchlistDataItems),function(){
                    self._reBindTickers(watchlistDataItems);
                    self.hideLoading();
                });
            }else{
                this._reBindTickers(watchlistDataItems);
                this.hideLoading();
            }
        }else{
            throw new Error("Invoke before, you need set watchlist id by setWatchlistId methed.");
        }
    },
    setSortICON:function(cols,sort){//for init
        this.grid.setSortICON(cols,sort);
    },
    updateData: function(tickerObject,aData,needFlash){  // this interface is provided to push client to invork.
        var symbol=tickerObject[GB_WL_KEY.FIELD](GB_WL_KEY.SYMBOL);
        //if the duplicate symbol is too much, it should update by tickerObject
        var rowIDs = GB_WLDM.getRowIdsBySymbol(this.currentId,symbol);
        if (rowIDs.length==0) {
            return;
        }
        for (var i = 0; i < rowIDs.length; i++) {
            var type, gValue, oValue,tag=false;
            for (var field in aData) {//
                type = aData[field].dataType;
                gValue = aData[field].hasOwnProperty("value")?aData[field].value:aData[field];
                oValue = aData[field].oValue;//include in data come from push system.
                //for update some data come from no push system
                if (Util.isValid(gValue,true,true)) {
                    tag =true;
                    this._updateDOM(rowIDs[i],field,gValue,oValue,type,needFlash);
                    this._bindUserData(rowIDs[i],field,gValue,oValue,type,needFlash);
                }
            }
            if(!tag){
                //if ticker is invalid, need calc some fixed value column  related user data, such as TotalCost
                this._bindUserData(rowIDs[i],"Name",null,null,"s",needFlash);
            }
        }
    },
	_getAlertData: function(rowid,value){//change the status of alert
        var imgSrc = "";
        if (value == 1) {
            imgSrc = "IM/alarm1.gif";
        }
        else 
            if (value == 0) {
                imgSrc = "IM/alarm0.gif";
            }
            else {
                return "";
            }
        return String.format('<a  href="javascript:this.callback.onAlertClick({0},{1});"><img src="{2}"/></a>',rowid,value,imgSrc);
    },
    hideLoading:function(){},
    showLoading:function(){},
    setWatchlistId:function(id){
        this.currentId = id;
        this._onStateChange("defaultWatchlist", id);
        GB_WLWM.activeWatchlist(this.subscribeID,id);//active watchlist
        this.haveAlert = false;//tag whether don't pup prompt
    },
    changeOutSize: function(w, h){
        var p = this.el;
        if (!w || !h) {
            this.grid.resize(Math.max(this.staticValue.minWidth,p.width()), this.config.autoWidth?null:p.height());
        }else{
            this.grid.resize(Math.max(w,this.staticValue.minWidth),this.config.autoHeight?null:h);
        }
        var self = this;
        this._reBindVisibleRows(true);
    },
    setConfig:function(config){
        $.extend(true,this.config,config);
    },
    stopSubscribe: function(){  // unsubscribe tickers.
        var tickers = [],rows =  this.config.subscribeInvisibleTickers ? this.grid.rows : this.visibleRows,wlitem=null;
        for (var r in rows) {
            if(!Util.isValid(rows[r],true,true)) continue;
             wlitem = this._getWatchlistDataItem(r)[GB_WL_KEY.ITEM];
            if(wlitem.isValid()){
                tickers.push(wlitem.getTickerObject());
            }
        }
        this._unsubscribeTicker(tickers);
    },
    clear:function(){
        for (var p in this) {
            if (!p || p == 'clear') {
                continue;
            }
            this[p] = null;
            delete this[p];
        }
    },
    show:function(){  // show grid.
        this.el.show();
    },
    hide:function(){
        this.el.hide();
    }
};
